#include "oled.h"

#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include "config.h"
#include "gps.h"
#include "radio.h"

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// UI 状态缓存 ------------------------------------------------------------------
static uint8_t sPage = 0;
static const uint8_t PAGE_COUNT = 5;
static unsigned long sLastSwitch = 0;
static unsigned long sLastDraw = 0;
static const unsigned long DRAW_MS = 400;

// Key debounce
static int sKeyPrev = HIGH;
static unsigned long sKeyChangeMs = 0;
static const unsigned long KEY_DEBOUNCE_MS = 60;

// Display instance
static Adafruit_SSD1306 sDisp(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Helpers ---------------------------------------------------------------------
static int16_t baseLineHeight() {
  int16_t h = 8 * OLED_TEXT_SIZE;
  if (h < 8) h = 8;
  return h;
}

static uint8_t linesPerPage() {
  int16_t base = baseLineHeight();
  int16_t gap = OLED_LINE_GAP;
  if (gap < 0) gap = 0;
  int16_t step = base + gap;
  if (step <= 0) step = base;
  int16_t usable = SCREEN_HEIGHT - base;
  if (usable < 0) usable = 0;
  uint8_t lines = 1 + (usable / step);
  if (lines == 0) lines = 1;
  return lines;
}

static uint8_t maxCharsPerLine() {
  int16_t charWidth = 6 * OLED_TEXT_SIZE;
  if (charWidth <= 0) charWidth = 6;
  return SCREEN_WIDTH / charWidth;
}

static String fitLine(const String &src) {
  uint8_t maxChars = maxCharsPerLine();
  if (src.length() <= maxChars) return src;
  if (maxChars <= 3) return src.substring(0, maxChars);
  return src.substring(0, maxChars - 3) + "...";
}

static void linePrint(const String &text, uint8_t &line) {
  if (line >= linesPerPage()) return;
  int16_t block = baseLineHeight() + OLED_LINE_GAP;
  if (block <= 0) block = baseLineHeight();
  int16_t y = line * block;
  sDisp.setTextSize(OLED_TEXT_SIZE);
  sDisp.setCursor(0, y);
  sDisp.print(fitLine(text));
  ++line;
}

static String sanitizeText(String text) {
  text.replace('\r', ' ');
  text.replace('\n', ' ');
  text.trim();
  return text;
}

// 将开机以来的毫秒数格式化为“XhYYm”的易读文本
static String formatUptime() {
  unsigned long totalSec = millis() / 1000UL;
  unsigned long hours = totalSec / 3600UL;
  unsigned long minutes = (totalSec % 3600UL) / 60UL;
  char buf[16];
  snprintf(buf, sizeof(buf), "%luh%02lum", hours, minutes);
  return String(buf);
}

static String formatUtcClock(const String &raw) {
  if (raw.length() < 6) return String(F("--:--:--"));
  int hh = raw.substring(0, 2).toInt();
  int mm = raw.substring(2, 4).toInt();
  int ss = raw.substring(4, 6).toInt();
  char buf[16];
  snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hh, mm, ss);
  return String(buf);
}

static String formatBeijingClock(const String &raw) {
  if (raw.length() < 6) return String(F("--:--:--"));
  int hh = raw.substring(0, 2).toInt();
  int mm = raw.substring(2, 4).toInt();
  int ss = raw.substring(4, 6).toInt();
  hh = (hh + 8) % 24;
  char buf[16];
  snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hh, mm, ss);
  return String(buf);
}

// Pages -----------------------------------------------------------------------
static void drawPageIdentity(const AppConfig &cfg) {
  sDisp.clearDisplay();
  sDisp.setTextColor(SSD1306_WHITE);
  sDisp.setTextWrap(false);
  uint8_t line = 0;

  bool wifiOk = (WiFi.status() == WL_CONNECTED);
  String ssid = wifiOk ? WiFi.SSID() : String(F("-"));
  String ip = wifiOk ? WiFi.localIP().toString() : String(F("-"));
  long rssi = wifiOk ? WiFi.RSSI() : 0;

  linePrint(String(F("CALL ")) + cfg.callsign, line);
  linePrint(String(F("SSID ")) + ssid, line);
  linePrint(String(F("WiFi ")) + (wifiOk ? F("CONNECTED") : F("DISCONNECTED")), line);
  linePrint(String(F("IP   ")) + ip, line);
  linePrint(String(F("RSSI ")) + (wifiOk ? String(rssi) : String(F("-"))), line);
  sDisp.display();
}

static void drawPageGps() {
  sDisp.clearDisplay();
  sDisp.setTextColor(SSD1306_WHITE);
  sDisp.setTextWrap(false);
  uint8_t line = 0;

  linePrint(String(F("FIX  ")) + (gpsHasFix() ? F("YES") : F("NO")), line);
  linePrint(String(F("LAT  ")) + String(gpsLatitude(), 5), line);
  linePrint(String(F("LON  ")) + String(gpsLongitude(), 5), line);
  String rawUtc = gpsLatestTime();
  linePrint(String(F("UTC  ")) + formatUtcClock(rawUtc), line);
  linePrint(String(F("BJ   ")) + formatBeijingClock(rawUtc), line);
  sDisp.display();
}

static void drawPageAprs(const AppConfig &cfg) {
  sDisp.clearDisplay();
  sDisp.setTextColor(SSD1306_WHITE);
  sDisp.setTextWrap(false);
  uint8_t line = 0;

  String comment = sanitizeText(cfg.statusText);
  if (!comment.length()) comment = String(F("-"));
  String symbol = String(cfg.symTable) + String(cfg.symChar);

  linePrint(String(F("Server ")) + cfg.aprsServer, line);
  linePrint(String(F("Port   ")) + String(cfg.aprsPort), line);
  linePrint(String(F("Symbol ")) + symbol, line);
  linePrint(String(F("Beacon ")) + String(cfg.beaconIntervalSec) + F("s"), line);
  linePrint(String(F("Comment ")) + comment, line);
  sDisp.display();
}

static void drawPageRadio(int pinSql, int pinPtt) {
  sDisp.clearDisplay();
  sDisp.setTextColor(SSD1306_WHITE);
  sDisp.setTextWrap(false);
  uint8_t line = 0;

  int sql = digitalRead(pinSql);
  int ptt = digitalRead(pinPtt);
  String lastMsg = sanitizeText(radioLastInfoText());
  if (!lastMsg.length()) lastMsg = String(F("-"));

  linePrint(String(F("SQL  ")) + (sql ? F("HIGH") : F("LOW")), line);
  linePrint(String(F("PTT  ")) + (ptt ? F("HIGH") : F("LOW")), line);
  linePrint(String(F("RX OK/ERR ")) + String(radioDecodeSuccessCount()) + F("/") + String(radioDecodeErrorCount()), line);
  linePrint(String(F("Last Msg ")) + lastMsg, line);
  linePrint(String(F("Msg Len ")) + String(lastMsg.length()), line);
  sDisp.display();
}

static void drawPageSystem() {
  sDisp.clearDisplay();
  sDisp.setTextColor(SSD1306_WHITE);
  sDisp.setTextWrap(false);
  uint8_t line = 0;

  bool wifiOk = (WiFi.status() == WL_CONNECTED);
  long rssi = wifiOk ? WiFi.RSSI() : 0;

  linePrint(String(F("Uptime ")) + formatUptime(), line);
  linePrint(String(F("WiFi RSSI ")) + (wifiOk ? String(rssi) : String(F("-"))), line);
  linePrint(String(F("GPS FIX ")) + (gpsHasFix() ? F("YES") : F("NO")), line);
  linePrint(String(F("RX OK/ERR ")) + String(radioDecodeSuccessCount()) + F("/") + String(radioDecodeErrorCount()), line);
  linePrint(String(F("Brand ")) + String(OLED_BRAND_TAG) + F(" ") + String(OLED_APP_VERSION), line);
  sDisp.display();
}

// 读取轮播间隔（使用宏可在编译期灵活调整）
static inline unsigned long rotateIntervalMs() {
  return OLED_ROTATE_MS;
}

static void drawCurrent(const AppConfig &cfg, int pinSql, int pinPtt) {
  switch (sPage % PAGE_COUNT) {
    case 0: drawPageIdentity(cfg); break;
    case 1: drawPageGps(); break;
    case 2: drawPageAprs(cfg); break;
    case 3: drawPageRadio(pinSql, pinPtt); break;
    case 4: drawPageSystem(); break;
    default: drawPageIdentity(cfg); break;
  }
}

// API ------------------------------------------------------------------------
void oledInit(int pinSda, int pinScl) {
  Wire.begin(pinSda, pinScl);
  Wire.setClock(400000);
  if (!sDisp.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("[ERROR] OLED init failed!"));
    return;
  }
  sDisp.clearDisplay();
  sDisp.setTextColor(SSD1306_WHITE);
  sDisp.setTextSize(OLED_TEXT_SIZE);
  sDisp.setTextWrap(false);
  sDisp.display();
}

void oledStartupStep(const __FlashStringHelper *title, uint8_t step, uint8_t total) {
  sDisp.clearDisplay();
  sDisp.setTextColor(SSD1306_WHITE);
  sDisp.setTextWrap(false);
  uint8_t line = 0;
  linePrint(F("APRS ESP32C3"), line);
  linePrint(String(title), line);

  const int barX = 0;
  const int barY = 54;
  const int barW = 128;
  const int barH = 10;
  sDisp.drawRect(barX, barY, barW, barH, SSD1306_WHITE);
  int denom = (total > 0) ? total : 1;
  int fillW = (int)((step * (barW - 2)) / denom);
  if (fillW < 0) fillW = 0;
  if (fillW > barW - 2) fillW = barW - 2;
  sDisp.fillRect(barX + 1, barY + 1, fillW, barH - 2, SSD1306_WHITE);
  sDisp.display();
}

void oledUpdate(const AppConfig &cfg, int pinKey, int pinSql, int pinPtt) {
  int ks = digitalRead(pinKey);
  unsigned long now = millis();
  if (ks != sKeyPrev) {
    if (now - sKeyChangeMs > KEY_DEBOUNCE_MS) {
      sKeyChangeMs = now;
      sKeyPrev = ks;
      if (ks == LOW) {
        sPage = (sPage + 1) % PAGE_COUNT;
        sLastSwitch = now;
        drawCurrent(cfg, pinSql, pinPtt);
      }
    }
  }

  if (now - sLastSwitch >= rotateIntervalMs()) {
    sLastSwitch = now;
    sPage = (sPage + 1) % PAGE_COUNT;
    drawCurrent(cfg, pinSql, pinPtt);
  }

  if (now - sLastDraw >= DRAW_MS) {
    sLastDraw = now;
    drawCurrent(cfg, pinSql, pinPtt);
  }
}
